package org.springboot.redis.config;

import java.time.Duration;

import org.springboot.redis.service.listener.RedisTemplateReceiver;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.cache.CacheKeyPrefix;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.jedis.JedisClientConfiguration;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.listener.PatternTopic;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
import org.springframework.data.redis.listener.adapter.MessageListenerAdapter;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import redis.clients.jedis.JedisPoolConfig;

@Configuration
@EnableCaching
public class RedisConfig {

	@Value("${spring.redis.tmpl.host}")
	private String tmplHost;
	@Value("${spring.redis.tmpl.port}")
	private Integer tmplPort;
	@Value("${spring.redis.tmpl.password}")
	private String tmplPassword;
	@Value("${spring.redis.tmpl.database}")
	private Integer tmplDatabase;
	@Value("${spring.redis.pool.tmpl.max-active}")
	private Integer tmplMaxActive;	
	
	@Value("${redis.template.topic}")
    private String tmplTopic;
	
	/**
	 * 如果 key 和 value 都使用的 StringRedisSerializer 序列化器，则推荐使用 StringRedisTemplate
	 * 配置 Redis 的 Key 和 Value 的序列化器
	 * 
	 * @param connectionFactory 从容器中获取 RedisTemplate
	 * @return 
	 */
	@Bean
	@Primary
	public RedisTemplate<Object, Object> redisStrTemplate(@Qualifier("FirstRedisConnectionFactory") RedisConnectionFactory connectionFactory) {
		RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<Object, Object>();
		redisTemplate.setConnectionFactory(connectionFactory);
		
	    // StringRedisSerializer 常用来序列化 Key，也可以用来序列化 Value
		StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
		// Jackson2JsonRedisSerializer 常用来直接序列化 Value 对象为 JSON 字符串。内部使用 ObjectMapper
		// 如果手动将 Value 转换成了 JSON，就不要再用 JSON 序列化器了
		Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
		
		redisTemplate.setKeySerializer(stringRedisSerializer);
	    redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
	    // 设置 hash key 和 value 序列化模式
        redisTemplate.setHashKeySerializer(stringRedisSerializer);
        redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
	    return redisTemplate;
	}
	
	@Bean(name = "FirstRedisConnectionFactory")
    public RedisConnectionFactory firstRedisConnectionFactory() {
        return createConnectionFactory(tmplDatabase, tmplHost, tmplPort, tmplMaxActive);
    }
	
	@Bean
    public RedisMessageListenerContainer listenerContainer(@Qualifier("FirstRedisConnectionFactory") RedisConnectionFactory factory, MessageListenerAdapter messageListenerAdapter){
        RedisMessageListenerContainer container = new RedisMessageListenerContainer();
        container.setConnectionFactory(factory);
        container.addMessageListener(messageListenerAdapter, new PatternTopic(tmplTopic));
        return container;
    }

    @Bean
    public MessageListenerAdapter messageListenerAdapter(RedisTemplateReceiver redisReceiver){
        MessageListenerAdapter messageListenerAdapter = new MessageListenerAdapter(redisReceiver, "onMessage");
        return messageListenerAdapter;
    }
    
    @Bean(name = "FirstCacheManager")
    @Primary
    public RedisCacheManager cacheManager(@Qualifier("FirstRedisConnectionFactory") RedisConnectionFactory connectionFactory) {
        RedisCacheConfiguration cacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
                .computePrefixWith(new CacheKeyPrefix() {
                    @Override
                    public String compute(String cacheName) {
                        return cacheName + ":";
                    }
                })	// 将::改写为1个
                .entryTtl(Duration.ofSeconds(60))	// 设置缓存过期时间
                .disableCachingNullValues()			// 禁用缓存空值，不缓存null校验
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(
                        new GenericJackson2JsonRedisSerializer()
                ));	// 设置 CacheManager 的值序列化方式为 json 序列化，可使用加入 @Class 属性

        // 使用 RedisCacheConfiguration 创建 RedisCacheManager
        RedisCacheManager cm = RedisCacheManager.builder(connectionFactory)
                .cacheDefaults(cacheConfiguration)
                .build();
        return cm;
    }

	private JedisConnectionFactory createConnectionFactory(Integer database, String host, int port, int poolMaxActive) {
		RedisStandaloneConfiguration standaloneConfiguration = new RedisStandaloneConfiguration();
		standaloneConfiguration.setHostName(host);
		standaloneConfiguration.setPort(port);
		standaloneConfiguration.setDatabase(database);
		// 获得默认的连接池构造器
        JedisClientConfiguration.JedisPoolingClientConfigurationBuilder jpcb = (JedisClientConfiguration.JedisPoolingClientConfigurationBuilder)JedisClientConfiguration.builder();
        // 指定 jedisPoolConifig 修改默认的连接池构造器
        JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
        jedisPoolConfig.setMaxTotal(poolMaxActive);
        jpcb.poolConfig(jedisPoolConfig);
        // 通过构造器来构造 jedis 客户端配置
        JedisClientConfiguration jedisClientConfiguration = jpcb.build();
        JedisConnectionFactory jedisConnectionFactory = new JedisConnectionFactory(standaloneConfiguration, jedisClientConfiguration);
        jedisConnectionFactory.afterPropertiesSet();
        return jedisConnectionFactory;
    }
}
